5.1 Base-station side: Carrier frequency offset (CFO) estimation and correction in 5G networks

The air interface of 5G NR is based on orthogonal frequency division multiplexing (OFDM), which divides the wideband carrier into small orthogonal subcarriers. Unlike the wideband signal, these subcarriers experience flat fading, resulting in significantly reduced receiver complexity and improved spectral efficiency. However, OFDM is very sensitive to time and frequency synchronization errors. These errors can arise in the frequency or time domains due to carrier frequency offset (CFO) and symbol time offset (STO), respectively. This experiment will focus on the carrier frequency offset and its impact on the system, as well as algorithms to estimate and correct the CFO using the synchronization signal block (SSB).

5.1 Import Libraries

5.1 Import Python and SDR Libraries

[1]:
# %matplotlib widget

import os
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'

import numpy as np
import tensorflow as tf
import sionna as sn
import adi
import matplotlib.pyplot as plt

import matplotlib.patches as patches
import matplotlib.animation as animation

5.1 Toolkit Libraries

[2]:
import sys
sys.path.append("../../../../")

from toolkit5G.SequenceGeneration import PSS, SSS, DMRS
from toolkit5G.PhysicalChannels   import PBCH, PBCHDecoder
from toolkit5G.ResourceMapping    import SSB_Grid, ResourceMapperSSB
from toolkit5G.OFDM               import OFDMModulator
from toolkit5G.MIMOProcessing     import AnalogBeamforming
from toolkit5G.Configurations     import TimeFrequency5GParameters, GenerateValidSSBParameters
from toolkit5G.OFDM               import OFDMDemodulator
from toolkit5G.ReceiverAlgorithms import PSSDetection, SSSDetection, ChannelEstimationAndEqualizationPBCH, DMRSParameterDetection, CarrierFrequencyOffsetEstimation
from toolkit5G.Configurations     import TimeFrequency5GParameters

from toolkit5G.SequenceGeneration import PSS, SSS, DMRS

5.1 Simulation Parameters

[3]:
# System Parameters
center_frequency    = 1e9     # Carrier frequency for signal transmission

# OFDM Parameters
Bandwidth           = 5*10**6 # bandwidth
fftSize             = 1024    # FFT-size for OFDM
subcarrier_spacing  = 15000   # Subcarrier spacing
numOFDMSymbols      = 14      # Number of OFDM symbols considered for emulation | 1 slot
sample_rate         = fftSize*subcarrier_spacing # sample rate required by OFDM and DAC/ADC of SDR

# Pulse Shaping
numSamplesPerSymbol = 1

# number of samples returned per call to rx()
buffer_size         = int(4*fftSize*1.2*numSamplesPerSymbol*numOFDMSymbols)

5.1 PBCH Payload Generation: MIB + ATI

[4]:
nSymbolFrame= 140*int(subcarrier_spacing/15000);   # Number of OFDM symbols per frame (Its a function of subcarrier spacing)

## This class fetches valid set of 5G parameters for the system configurations
tfParams    = TimeFrequency5GParameters(Bandwidth, subcarrier_spacing, fftsize = fftSize)
tfParams(nSymbolFrame, typeCP = "normal")
nRB         = tfParams.numRBs        # SSB Grid size (Number of RBs considered for SSB transition)
Neff        = tfParams.Neff          # Number of resource blocks for Resource Grid ( exclude gaurd band | offsets : BWP)
fftsize     = tfParams.fftsize       # FFT-size for OFDM
lengthCP    = tfParams.lengthCP      # CP length

#### Generate MIB Information
lamda                           = 3e8/center_frequency;
nSCSOffset                      = 1
ssbParameters                   = GenerateValidSSBParameters(center_frequency, nSCSOffset, isPairedBand = False,
                                                             intraFrequencyReselection = "allowed",
                                                             withSharedSpectrumChannelAccess = False,
                                                             ssbSubCarrierOffset = 0)

systemFrameNumber               = ssbParameters.systemFrameNumber
subCarrierSpacingCommon         = ssbParameters.subCarrierSpacingCommon
ssbSubCarrierOffset             = ssbParameters.ssbSubCarrierOffset #ssbParameters.ssbSubCarrierOffset
DMRSTypeAPosition               = ssbParameters.DMRSTypeAPosition
controlResourceSet0             = ssbParameters.controlResourceSet0
searchSpace0                    = ssbParameters.searchSpace0

isPairedBand                    = ssbParameters.isPairedBand
nSCSOffset                      = ssbParameters.nSCSOffset
choiceBit                       = ssbParameters.choiceBit
ssbType                         = ssbParameters.ssbType
nssbCandidatesInHrf             = 4     #ssbParameters.nssbCandidatesInHrf
ssbIndex                        = ssbParameters.ssbIndex
hrfBit                          = ssbParameters.hrfBit
cellBarred                      = ssbParameters.cellBarred
intraFrequencyReselection       = ssbParameters.intraFrequencyReselection
withSharedSpectrumChannelAccess = ssbParameters.withSharedSpectrumChannelAccess

nFrames                         = 0.5
Nsc_ssb                         = 240  # Numbers of subcarriers in SSB
Nsymb_ssb                       = 4    # Numbers of OFDM symbols in SSB

5.1 PSS, SSS, PBCH, DMRS Generation

[5]:
# Generate cell-ID 2 for PSS generation and cell-ID computation
N_ID2        = np.random.randint(3)

# Generate PSS sequence
pssObject    = PSS(N_ID2);
pssSequence  = pssObject()

# Generate cell-ID 1 for SSS generation and cell-ID computation
N_ID1        = np.random.randint(336)
N_ID         = 3*N_ID1 + N_ID2

# Generate SSS sequence
sssObject    = SSS(N_ID1, N_ID2);
sssSequence  = sssObject()

# Generate DMRS sequence
dmrsLen      = 144;
dmrsObject   = DMRS("PBCH", N_ID, ssbIndex, nssbCandidatesInHrf, hrfBit)
dmrsSequence = dmrsObject(dmrsLen)

# Generate PBCH symbols
pbchObject   = PBCH(center_frequency, choiceBit, subCarrierSpacingCommon, DMRSTypeAPosition,
                   controlResourceSet0, searchSpace0, cellBarred, intraFrequencyReselection,
                   systemFrameNumber, ssbSubCarrierOffset, hrfBit, ssbIndex, N_ID,
                   nssbCandidatesInHrf)

pbchSymbols  = pbchObject()

5.1 Constellation Diagram: Base Station

[6]:
fig, ax = plt.subplots()
ax.scatter(np.real(pbchSymbols),  np.imag(pbchSymbols), s=48)
ax.grid()
ax.axhline(y=0, ls=":", c="k")
ax.axvline(x=0, ls=":", c="k")
ax.set_xlim([-1,1])
ax.set_ylim([-1,1])
ax.set_xlabel("Real {x}")
ax.set_ylabel("Imag {x}")
ax.set_title("Constellation Diagram: QPSK")
plt.show()
../../../../_images/api_Content_Codes_Tutorial-5%5BCoarse_CFO_estimation_and_Correction%5D_5.BS_side-Carrier_frequency_offset_%28CFO%29_estimation_and_correction_in_5G_networks_11_0.png

5.1 SSB Generation

[7]:

## Generate SSB Object ssbObject = SSB_Grid(N_ID, True) ssb = ssbObject(pssSequence, sssSequence, dmrsSequence, pbchSymbols) # Loading SSB to Resource Grid ##################################### # ssbPositionInBurst = np.ones(nssbCandidatesInHrf, dtype=int) ssbPositionInBurst = np.zeros(nssbCandidatesInHrf, dtype=int) ssbPositionInBurst[0] = 1 ssbRGobject = ResourceMapperSSB(ssbType=ssbType, carrierFrequency = center_frequency, isPairedBand = isPairedBand, withSharedSpectrumChannelAccess = withSharedSpectrumChannelAccess) ssbGrid = ssbRGobject(ssb[0], ssbPositionInBurst, offsetInSubcarriers = 0, offsetInRBs = 0, numRBs = nRB)[0:14] fig, ax = ssbObject.displayGrid(option=1)
../../../../_images/api_Content_Codes_Tutorial-5%5BCoarse_CFO_estimation_and_Correction%5D_5.BS_side-Carrier_frequency_offset_%28CFO%29_estimation_and_correction_in_5G_networks_13_0.png

5.1 OFDM Implementation: Transmission of SSB

[8]:
## Loading SSB to Resource Grid
numofGuardCarriers = (int((fftsize - Neff)/2), int((fftsize - Neff)/2))
offsetToPointA     = 0
firstSCIndex       = int(numofGuardCarriers[0] + offsetToPointA)


X = np.zeros((numOFDMSymbols, fftsize), dtype= np.complex64)  # Generating grid of size 14 X  FFT_Size.
X[:, firstSCIndex:firstSCIndex+ssbGrid.shape[-1]] = ssbGrid   # Loading SSB into grid.

### OFDM Modulation at Transmitter
modulator = OFDMModulator(lengthCP[1]) # OFDM modulation
x_time    = modulator(X).flatten()     # Time domain samples from OFDM modulation.


### Plot Resource Grid
fig, ax = plt.subplots()
plt.imshow(np.abs(X), cmap = 'hot', interpolation='nearest', aspect = "auto")
ax = plt.gca();
ax.grid(color='c', linestyle='-', linewidth=1)
ax.set_xlabel("Subcarrier-Index (k)")
ax.set_ylabel("OFDM Symbol Index (n)")
ax.set_title("Heat map of Transmit Grid")
plt.show()

../../../../_images/api_Content_Codes_Tutorial-5%5BCoarse_CFO_estimation_and_Correction%5D_5.BS_side-Carrier_frequency_offset_%28CFO%29_estimation_and_correction_in_5G_networks_15_0.png

5.1 SDR Setup

[9]:

# SDR setup sdr = adi.Pluto("ip:192.168.3.1") # Create object of SDR setup object and configure the IP of SDR connect to the system sdr.sample_rate = int(sample_rate) # Sets the sample rate for the ADC/DAC of the SDR. # Config Tx sdr.tx_rf_bandwidth = int(sample_rate) # Set the bandwidth of the transmit filter | Can be set same as the sample rate # For Pluto SDR, tx_rf_bandwidth should be between 200 kHz and 56 MHz. sdr.tx_lo = int(center_frequency) # Sets the transmitter local oscillator frequency. The carrier is used to modulate/up-convert the analog information signal. # For Pluto SDR, tx_lo can take a value between 325 MHz to 3.8 GHz. sdr.tx_hardwaregain_chan0 = 0 # Sets the gain (dB) of the transmitter power amplifier. The higher the value the more the power radiated by antenna. # For Pluto SDR, tx_hardwaregain_chan0 can take values between -90 to 0.

5.1 Transmission of samples

[10]:
# np.max(np.abs((1.4*2**17*x_time)))
[11]:
# Start the transmitter
sdr.tx_cyclic_buffer = True # Enable cyclic buffers

sdr.tx(1.4*2**17*(x_time.repeat(1))) # start transmitting
[ ]: